import { useMemo } from 'react'
import { match } from 'ts-pattern'

import { ErrorTrackingException, ErrorTrackingStackFrame } from '../types'
import { KnownException, KnownExceptionRegistry } from './known-exceptions'

type StackTraceRenderer = (
    frames: ErrorTrackingStackFrame[],
    exception: ErrorTrackingException,
    knownException?: KnownException
) => React.ReactNode

export type ExceptionRendererProps = {
    className?: string
    exception: ErrorTrackingException
    frameFilter?: (frame: ErrorTrackingStackFrame) => boolean
    renderExceptionHeader: (exception: ErrorTrackingException) => React.ReactNode

    renderUndefinedTrace: (exception: ErrorTrackingException, knownException?: KnownException) => React.ReactNode
    renderResolvedTrace: StackTraceRenderer
    renderFilteredTrace: (exception: ErrorTrackingException, knownException?: KnownException) => React.ReactNode
}

export function ExceptionRenderer({
    className,
    exception,
    frameFilter,
    renderExceptionHeader,
    renderUndefinedTrace,
    renderResolvedTrace,
    renderFilteredTrace,
}: ExceptionRendererProps): JSX.Element {
    const knownException = useMemo(() => KnownExceptionRegistry.match(exception), [exception])
    return (
        <div className={className}>
            <div>{renderExceptionHeader(exception)}</div>
            <div>
                {match(exception.stacktrace)
                    .when(
                        (stack) => stack === null || stack === undefined || stack.frames.length === 0,
                        () => renderUndefinedTrace(exception, knownException)
                    )
                    .when(
                        (stack) => stack!.type === 'resolved',
                        (stack) => {
                            let frames = frameFilter ? stack!.frames.filter(frameFilter) : stack!.frames
                            return match(frames)
                                .when(
                                    (frames) => Array.isArray(frames) && frames.length > 0,
                                    (frames) => renderResolvedTrace(frames, exception, knownException)
                                )
                                .otherwise(() => renderFilteredTrace(exception, knownException))
                        }
                    )
                    .otherwise(() => null)}
            </div>
        </div>
    )
}
